﻿/*
 * @author: wizardc
 */

using System;
using System.Collections.Generic;
using Dou.Utils;

namespace Dou.Event
{
    /// <summary>
    /// 事件发送器
    /// </summary>
    public class EventDispatcher : IEventDispatcher
    {
        static EventDispatcher()
        {
            Recyclable.Register<EventBin>(50, () => new EventBin());
        }

        private Dictionary<string, List<EventBin>> _eventMap;
        private IEventDispatcher _target;

        public EventDispatcher(IEventDispatcher target = null)
        {
            _eventMap = new Dictionary<string, List<EventBin>>();
            _target = target;
        }

        public void On(string type, Action<DEvent> listener)
        {
            addEventListener(type, listener, false);
        }

        public void Once(string type, Action<DEvent> listener)
        {
            addEventListener(type, listener, true);
        }

        protected bool addEventListener(string type, Action<DEvent> listener, bool once)
        {
            if (!_eventMap.ContainsKey(type))
            {
                _eventMap.Add(type, new List<EventBin>());
            }
            List<EventBin> list = _eventMap[type];
            foreach (EventBin eventBin in list)
            {
                if (eventBin.listener == listener)
                {
                    return false;
                }
            }
            EventBin bin = Recyclable.Get<EventBin>();
            bin.listener = listener;
            bin.once = once;
            list.Add(bin);
            return true;
        }

        public bool Has(string type)
        {
            if (!_eventMap.ContainsKey(type))
            {
                return false;
            }
            List<EventBin> list = _eventMap[type];
            if (list.Count == 0)
            {
                return false;
            }
            foreach (EventBin eventBin in list)
            {
                if (eventBin != null)
                {
                    return true;
                }
            }
            return false;
        }

        public bool Dispatch(DEvent evt)
        {
            evt.SetTarget(_target ?? this);
            return Notify(evt);
        }

        protected bool Notify(DEvent evt)
        {
            if (!_eventMap.ContainsKey(evt.type))
            {
                return true;
            }
            List<EventBin> list = _eventMap[evt.type];
            if (list.Count == 0)
            {
                return true;
            }
            int currentIndex = 0, i = 0;
            for (i = 0; i < list.Count; i++)
            {
                EventBin bin = list[i];
                if (bin != null)
                {
                    if (bin.once)
                    {
                        Recyclable.Recycle(bin);
                        list[i] = null;
                    }
                    else
                    {
                        if (currentIndex != i)
                        {
                            list[currentIndex] = bin;
                            list[i] = null;
                        }
                        currentIndex++;
                    }
                    bin.listener(evt);
                }
            }
            if (currentIndex != i)
            {
                while (i < list.Count)
                {
                    list[currentIndex++] = list[i++];
                }
                list.RemoveRange(currentIndex, list.Count - currentIndex);
            }
            return !evt.IsDefaultPrevented();
        }

        public void Off(string type, Action<DEvent> listener)
        {
            if (_eventMap.ContainsKey(type))
            {
                List<EventBin> list = _eventMap[type];
                for (int i = 0; i < list.Count; i++)
                {
                    EventBin bin = list[i];
                    if (bin != null && bin.listener == listener)
                    {
                        Recyclable.Recycle(bin);
                        list[i] = null;
                        break;
                    }
                }
            }
        }
    }

    class EventBin : ICacheable
    {
        public Action<DEvent> listener;
        public bool once;

        public void OnRecycle()
        {
            listener = null;
            once = false;
        }

        public void OnReuse()
        {
        }
    }
}
